package vip.mtdp.asset.core.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.mtdp.asset.api.constants.AssetErrorCodeEnum;
import vip.mtdp.asset.api.constants.BizTypeEnum;
import vip.mtdp.asset.api.dto.AccountDTO;
import vip.mtdp.asset.core.dal.dao.AccountBalanceMapper;
import vip.mtdp.asset.core.dal.dao.AccountMapper;
import vip.mtdp.asset.core.dal.domain.Account;
import vip.mtdp.asset.core.dal.domain.AccountBalance;
import vip.mtdp.asset.core.dto.QueryBalancePageDTO;
import vip.mtdp.common.constant.CommonEnum;
import vip.mtdp.common.exception.AppRTException;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @Description 账户服务：账户开通，账户信息查询，余额管理
 * @Author Evans.wang
 * @since 2023/10/29 10:03
 **/
@Slf4j
@Service
public class AccountManager {

    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private AccountBalanceMapper accountBalanceMapper;


    /**
     * 创建账户,可同时创建余额
     * @param accountDTO
     * @return
     */
    @Transactional
    public AccountDTO create(AccountDTO accountDTO){
        Account account = new Account();
        BeanUtil.copyProperties(accountDTO, account);
        account.setState(CommonEnum.ENABLE.getCode());
        //保存账户信息
        int cnt = accountMapper.insert(account);

        List<AccountDTO.BalanceDTO> balanceDTOList = accountDTO.getBalanceDTOList();
        if (balanceDTOList != null && !balanceDTOList.isEmpty()) {
            //设置accountId
            balanceDTOList.parallelStream().forEach(e -> {
                e.setAccountId(account.getId());
            });
            //保存余额
            createBalance(balanceDTOList);
        }

        if (cnt != 1){
            throw new AppRTException("create account fail");
        }

        accountDTO.setId(account.getId());
        accountDTO.setState(account.getState());
        return accountDTO;
    }

    /**
     * 创建余额,需要指定accountId
     * @param balanceDTOList
     * @return
     */
    @Transactional
    public boolean createBalance(List<AccountDTO.BalanceDTO> balanceDTOList){
        List<AccountBalance> accountBalanceList = new ArrayList<>();
        balanceDTOList.forEach(e -> {
            AccountBalance accountBalance = new AccountBalance();
            BeanUtil.copyProperties(e, accountBalance);
            //状态
            accountBalance.setState(CommonEnum.ENABLE.getCode());
            Date d = new Date();
            accountBalance.setCreateTime(d);
            accountBalance.setUpdateTime(d);
            accountBalanceList.add(accountBalance);

            e.setState(CommonEnum.ENABLE.getCode());
        });
        int cnt = accountBalanceMapper.batchInsert(accountBalanceList);
        if (cnt != balanceDTOList.size()){
            throw new AppRTException("save balance fail");
        }
        return true;
    }


    /**
     * 冻结余额
     * @param accountId
     * @param coin
     * @param amount
     * @return
     */
    @Transactional
    public boolean frozenBalance(Long accountId, String coin, BigDecimal amount){
        AccountDTO.BalanceDTO balanceDTO = queryBalanceLockByAccountId(accountId, coin);
        BigDecimal frozenAmount = balanceDTO.getFrozenBalance().add(amount);
        BigDecimal balance = balanceDTO.getBalance();
        if (balance.compareTo(frozenAmount) < 0 && CommonEnum.NO.getCode().equals(balanceDTO.getAllowNegative())){
            throw new AppRTException("balance not sufficient funds");
        }
        balanceDTO.setFrozenBalance(frozenAmount);
        balanceDTO.setBalance(balance.subtract(amount));
        return updateBalanceById(balanceDTO);
    }

    /**
     * 解冻余额
     * @param accountId
     * @param coin
     * @param amount
     * @return
     */
    @Transactional
    public boolean unFrozenBalance(Long accountId, String coin, BigDecimal amount){
        AccountDTO.BalanceDTO balanceDTO = queryBalanceLockByAccountId(accountId, coin);
        BigDecimal frozenAmount = balanceDTO.getFrozenBalance().subtract(amount);
        if (frozenAmount.compareTo(BigDecimal.ZERO) < 0){
            throw new AppRTException("frozen balance greater than or equal to zero");
        }
        BigDecimal balance = balanceDTO.getBalance();
        balanceDTO.setFrozenBalance(frozenAmount);
        balanceDTO.setBalance(balance.add(amount));
        return updateBalanceById(balanceDTO);
    }

    /**
     * 更新账户信息
     * @param accountDTO
     * @return
     */
    @Transactional
    public boolean updateAccount(AccountDTO accountDTO){
        Long accountId = accountDTO.getId();
        if (accountId == null){
            throw new AppRTException("account id not null");
        }
        Account account = new Account();
        account.setId(accountId);
        account.setName(accountDTO.getName());
        account.setLevel(accountDTO.getLevel());
        account.setState(accountDTO.getState());
        return accountMapper.updateById(account) == 1;
    }

    /**
     * 更新余额
     * @param balanceDTO
     * @return
     */
    @Transactional
    public boolean updateBalance(AccountDTO.BalanceDTO balanceDTO){
        Long balanceId = balanceDTO.getId();
        if (balanceId == null){
            throw new AppRTException("balance id is not null");
        }
        //余额逻辑校验
        checkBalance(balanceDTO);

        AccountBalance accountBalance = accountBalanceMapper.selectById(balanceId);
        if (accountBalance == null){
            throw new AppRTException("account balance is not exist");
        }
        //余额检查
        BigDecimal checkBalance = accountBalance.getBalanceDr().subtract(accountBalance.getBalanceCr()).subtract(accountBalance.getFrozenBalance());
        if (accountBalance.getBalance().compareTo(checkBalance) != 0){
            log.warn("===> balance wrong. balanceId = {}", balanceId);
            throw new AppRTException("balance wrong.");
        }
        return updateBalanceById(balanceDTO);
    }


    /**
     *
     * 查询账户信息
     * @param id
     * @return
     */
    public AccountDTO queryAccountById(Long id){
        Account account = accountMapper.selectById(id);
        if (account == null){
            throw new AppRTException("account not exist");
        }
        AccountDTO accountDTO = new AccountDTO();
        BeanUtil.copyProperties(account, accountDTO);
        return accountDTO;
    }

    /**
     *
     * 查询账户信息
     * @param vaultId
     * @param chain
     * @param no
     * @return
     */
    public AccountDTO queryAccountByNo(Long vaultId, String chain, String no){
        QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(Account::getVaultId, vaultId).eq(Account::getChain, chain).eq(Account::getNo, no);
        Account account = accountMapper.selectOne(queryWrapper);
        if (account == null){
            throw new AppRTException("account not exist");
        }
        AccountDTO accountDTO = new AccountDTO();
        BeanUtil.copyProperties(account, accountDTO);
        return accountDTO;
    }

    /**
     * 查询账户余额
     * @param vaultId
     * @param chain
     * @param no
     * @return
     */
    public List<AccountDTO.BalanceDTO> queryBalanceByNo(Long vaultId, String chain, String no){
        QueryWrapper<AccountBalance> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(AccountBalance::getVaultId, vaultId)
                .eq(AccountBalance::getChain, chain)
                .eq(AccountBalance::getNo ,no);
        List<AccountBalance> accountBalanceList = accountBalanceMapper.selectList(queryWrapper);

        List<AccountDTO.BalanceDTO> balanceDTOList = new ArrayList<>();
        if (accountBalanceList != null && !accountBalanceList.isEmpty()){
            accountBalanceList.forEach(e -> {
                AccountDTO.BalanceDTO balanceDTO = new AccountDTO.BalanceDTO();
                BeanUtil.copyProperties(e, balanceDTO);
                balanceDTOList.add(balanceDTO);
            });
        }
        return balanceDTOList;
    }

    /**
     * 查询账户余额
     * @param queryBalancePageDTO
     * @return
     */
    public List<AccountDTO.BalanceDTO> queryBalanceByPage(QueryBalancePageDTO queryBalancePageDTO){
        //自定义分页
        List<AccountBalance> accountBalanceList = accountBalanceMapper.selectByPage(queryBalancePageDTO);

        List<AccountDTO.BalanceDTO> balanceDTOList = new ArrayList<>();
        if (accountBalanceList != null && !accountBalanceList.isEmpty()){
            accountBalanceList.forEach(e -> {
                AccountDTO.BalanceDTO mBalanceDTO = new AccountDTO.BalanceDTO();
                BeanUtil.copyProperties(e, mBalanceDTO);
                balanceDTOList.add(mBalanceDTO);
            });
        }
        return balanceDTOList;
    }


    /**
     * 根据账户id+coin查询余额信息
     * @param accountId
     * @param coin
     * @return
     */
    public AccountDTO.BalanceDTO queryBalanceLockByAccountId(Long accountId, String coin){
        QueryWrapper<AccountBalance> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(AccountBalance::getAccountId, accountId).eq(AccountBalance::getCoin, coin).last(" for update");
        AccountBalance accountBalance = accountBalanceMapper.selectOne(queryWrapper);
        if (accountBalance == null){
            throw new AppRTException(AssetErrorCodeEnum.BALANCE_NOT_EXIST.getCode(), AssetErrorCodeEnum.BALANCE_NOT_EXIST.getLabel());
        }
        AccountDTO.BalanceDTO balanceDTO = new AccountDTO.BalanceDTO();
        BeanUtil.copyProperties(accountBalance, balanceDTO);
        return balanceDTO;
    }

    /**
     * 锁定账户
     * @param id
     */
    public void lockBalance(Long id){
        QueryWrapper<AccountBalance> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(AccountBalance::getId, id).last(" for update");
        accountBalanceMapper.selectOne(queryWrapper);
    }

    /**
     * 余额校验
     * @param balanceDTO
     */
    private void checkBalance(AccountDTO.BalanceDTO balanceDTO){
        BigDecimal checkBalance = balanceDTO.getBalanceDr().subtract(balanceDTO.getBalanceCr()).subtract(balanceDTO.getFrozenBalance());
        if (balanceDTO.getBalance().compareTo(checkBalance) != 0){
            log.warn("===> balance wrong. balanceId = {}", balanceDTO.getId());
            throw new AppRTException("balance wrong.");
        }
    }

    /**
     * 根据id更新余额
     * @param balanceDTO
     * @return
     */
    private boolean updateBalanceById(AccountDTO.BalanceDTO balanceDTO){
        AccountBalance mAccountBalance = new AccountBalance();
        mAccountBalance.setId(balanceDTO.getId());
        mAccountBalance.setBalance(balanceDTO.getBalance());
        mAccountBalance.setBalanceCr(balanceDTO.getBalanceCr());
        mAccountBalance.setBalanceDr(balanceDTO.getBalanceDr());
        mAccountBalance.setFrozenBalance(balanceDTO.getFrozenBalance());
        return accountBalanceMapper.updateById(mAccountBalance) == 1;
    }
}
